#---------------------------------------
#
#Script: peopletomethods.py
#Description: assumes data about commits and symbols and joins them so we have information
#             on which person made what change to which functions, how many lines, and in what file.
#
#Author: Alex Gourley (acgourley@ucdavis.edu)
#
#---------------------------------------

import os
import _pg
import re
import sys 
import popen2
import revcalc
import repository

def getToDoHash(db):
	#load the files + revisions from sql, put into a hash keyed by filename.
	#the keys refer to lists of file revision pairs, where each pair is an previous and subsequent revision. 
	try:
		pgo = db.query("select Filename, Revision from files ORDER BY datetime desc");
	except Error:
		print "error! could not get data from table commits"
	#stores data about what files we haven't mined data on yet
	todoHash = {}
	if(pgo != None):
		results = pgo.getresult()
		for result in results:
			filename = result[0]
			revision = result[1]
			if(todoHash.has_key(filename)):
				todoHash[filename].addRev(revision)
			else:
				todoHash[filename] = repository.RepFile(filename)
				todoHash[filename].addRev(revision)
	else:
		print "Could not get commit data from database!"

	return todoHash

#input is a hash with function names pointing to a function class
def calcChangeAmount(functionList, start, end):
	"""calculates what functions were changed and by how much, give a list of functions and the change points"""
	
	changedFuncLines = []
	for function in functionList:
		if (start >= function.start and end <= function.end):
			#starts in func, ends in func
			changedFuncLines.append([function.funcID, end - start + 1])
			continue
		if (start >= function.start and start <= function.end and end > function.end):
			#starts in func i, but doesn't end in it
			changedFuncLines.append([function.funcID, function.end - start + 1])
			continue
		if (start < function.start and end >= function.start and end <= function.end):
			#ends in function i, but doesn't start in it
			changedFuncLines.append([function.funcID, end - function.start + 1])
			continue
		if (start < function.start and end > function.end):
			#changes encompass function
			changedFuncLines.append([function.funcID, function.end - function.start + 1])
			continue 
	return changedFuncLines #[funcid, lines changed]
	
def parseDiff(file):
	"""takes file object, returns list of changes"""
	#file = open(filename)
	result = []
	for line in file:
		mo = re.match("^\d", line)#look for a line with a number as the first char
		if mo:
			line = re.sub('\n', '', line) #strip endl
			linesPair = re.split('[dca]', line) #break in half
			linesPair[0] = re.split(',',linesPair[0]) #store first num set
			linesPair[1] = re.split(',',linesPair[1]) #store second num set
			
			#convert to numbers here
			if len(linesPair[0]) == 1:
				#one line
				linesPair[0][0] = int(linesPair[0][0], 10)
				#groom the data so we always have a 2x2 array
				linesPair[0].append(linesPair[0][0])
			elif len(linesPair[0]) == 2:
				linesPair[0][0] = int(linesPair[0][0], 10)
				linesPair[0][1] = int(linesPair[0][1], 10)
			else:
				print "ERROR! Did not parse diff file correctly!"
			if len(linesPair[1]) == 1:
				#one line
				linesPair[1][0] = int(linesPair[1][0], 10)
				#groom the data so we always have a 2x2 array
				linesPair[1].append(linesPair[1][0])
			elif len(linesPair[1]) == 2:
				linesPair[1][0] = int(linesPair[1][0], 10)
				linesPair[1][1] = int(linesPair[1][1], 10)
			else:
				print "ERROR! Did not parse diff file correctly!"
			#now we have a list of [[line start, line end],[line start, line end]]
			#find out what they did? Add one more entry to the list
			if(line.count('a') > 0):
				#we're adding
				linesPair.append('a')
				pass
			if(line.count('d') > 0):
				#we're deleteing
				linesPair.append('d')
				pass
			if(line.count('c') > 0):
				#we're changing	
				linesPair.append('c')
				pass
			
			result.append(repository.Change(linesPair[0], linesPair[1], linesPair[2]))
	return result
			
def mapPeopleToMethods(db, rep, log):
	"""fill in change/changedetails tables"""
	
	fileHash = {}
	
	#instantiate a fileHash with every file id
	s = "select FileID from Files"
	pgo = db.query(s)
	fileIDList = pgo.getresult()
	for fid in fileIDList:
		fileHash[fid[0]] = []

	#we want to store all the function start locations, so we create a hash of fileid's that point to a list of function classes
	s = "select distinct FileID from Functions"
	pgo = db.query(s)
	fileIDList = pgo.getresult()
	
	#loop files
	for fileIDRow in fileIDList:
		fileID = fileIDRow[0]
		
		#print "reading in data for fileid:", fileName, fileID, revision
		
		#get function data, put in fileHash
		s = "select funcID, name, startline from Functions where FileID = " + str(fileID) + " order by startline"
		pgo = db.query(s)
		
		funcTable = pgo.getresult()
		if funcTable != []:#not all files have functions
			
			for funcData in funcTable:
				funcID = funcData[0]
				funcName = funcData[1]
				funcStart = funcData[2]
				fileHash[fileID].append(respository.Function(funcID, funcName, funcStart, 0))
				
			#using the start line and name data, fill in the end locations for the functions
			numFuncs = len(fileHash[fileID])
			
			for i in range(numFuncs - 1):
				fileHash[fileID][i].end = fileHash[fileID][i+1].start - 1
			
			if(len(fileHash[fileID]) > 0):
				fileHash[fileID][numFuncs - 1].end = 33000
			else:
				print "this really shouldn't happen....debug time"
				sys.exit()
				
	todoHash = getToDoHash(db)	

	

	#update every file to 1.1, so that they all exist at once in the working copy (otherwise we can't update them)
	scriptPath = os.getcwd()
	os.chdir(rep.path)
	rep.prepUpdate()
	
	count = 0
	#loop untill we're out of rev's for the files in the hash 
	ChangeID = 0
	while True:
		#count how many layers of revs we peel off (how many times we have run lxr :) )
		count += 1
		delList = [] #clear list to store hash keys scheduled to be deleted
		
		#loop over all the items in the hash table: f->filename (key) and c->Commit class
		#go into repository dir
		
		for f,c in todoHash.iteritems():			
			try:
				#store the current revision pair in the class so we can log it
				#also the pop call means we have shortened revPairList a notch
				c.curFileRev = c.fileRevList.pop()
                if c.curFileRev[0] == None:
                    #if initial rev = None and nextrev is the first rev of the file
                    continue
			except IndexError:
				#this file has no more revisions we need to log
				#so schedule it to be deleted after the iteration
				delList.append(f)
				continue
									
		#delete all the files that have no more revisions to do
		for i in delList:
			del todoHash[i]
		
		#if we deleled the last of them: end loop, we're out of stuff to do.
		if len(todoHash.keys()) == 0:
			break
					
		#run diff on all file/revision pairs to gather line change data	
		for filename, repFile in todoHash.iteritems():
			
			print "============================="
			
			s = "select fileid from files where filename = '"+ filename +"' and revision = '"+ repFile.curFileRev.finalRev +"'"
			print s
			pgo = db.query(s)
			finalFileID = pgo.getresult()[0][0]
			
			#edge case...should investigate what would cause this
			#if repFile.curFileRev.initialRev == '1.0':
			#	continue
			
			s = "select fileid from files where filename = '"+ filename +"' and revision = '"+ repFile.curFileRev.initialRev +"'"
			print s
			pgo = db.query(s)
			
			i
			initialFileID = pgo.getresult()[0][0]
			
			
			#create a change entry in sql
			ChangeID += 1
			s = "insert into changes VALUES("+ str(ChangeID) +","+ str(initialFileID) +","+ str(finalFileID) +")"
			print s
			db.query(s)
			
			#if not fileHash.has_key(fileName):
			#	print "file", fileName, "has no functions, continuing"
			#	continue
			
			#get diff changes
			diffOutput = repostory.diff(repFile.curFileRev.initialRev, repFile.curFileRev.finalRev, filename)
			#s = ' cvs diff -r ' + cvsFile.curFileRev.initialRev +' -r '+ cvsFile.curFileRev.finalRev +' '+ filename
			#print s
			#os.system(s)
			#diffOutput = popen2.popen2(s)[0]
			#read in diff output, put in a list of Change classes
			changeList = parseDiff(diffOutput)
			#now we have to figure out which of these have been modifed
			changedFuncLines = []
			
			#get a set of function names from each revision
			#initialSet = set(fileHash[fileName][file.curFileRev.initialRev]["names"])
			#afterSet = set(fileHash[fileName][file.curFileRev.finalRev]["names"])

			#check for special case where we added a function, put result in addedFunctions
			#addedFunctions = afterSet - initialSet
			
			
			
			
			#loop changes from diff initial rev to final rev
			addedLines = []
			deletedLines = []
			for change in changeList:
				#print change.before, change.after, change.changeType
				if change.changeType == 'a':
					#addition, check in final revision
				
					addedLines = calcChangeAmount(fileHash[finalFileID], change.after[0], change.after[1])
				elif change.changeType == 'd':
					#deletion, check in inital revision
					
					deletedLines = calcChangeAmount(fileHash[initialFileID], change.before[0], change.before[1])
				elif change.changeType == 'c':
					#change, line counts = add and sub
					addedLines = calcChangeAmount(fileHash[finalFileID], change.after[0], change.after[1])
					deletedLines = calcChangeAmount(fileHash[initialFileID], change.before[0], change.before[1])
				for added in addedLines:
					print added
					s = "insert into changedetails(ChangeID, FuncID, LinesChanged, ChangeType) VALUES("+ str(ChangeID) +","+ str(added[0]) + "," + str(added[1]) + ", 'a')"
					print s
					db.query(s)
				for deleted in deletedLines:
					print deleted
					s = "insert into changedetails(ChangeID,FuncID, LinesChanged, ChangeType) VALUES("+ str(ChangeID) +","+ str(deleted[0]) +","+ str(deleted[1]) + ", 'd')"
					print s
					db.query(s)
				
			
	os.chdir(scriptPath)
	print count
